home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / BOI200P.ZIP / IOLIB.PAS < prev    next >
Pascal/Delphi Source File  |  1992-12-14  |  44KB  |  1,070 lines

  1. { $D-}
  2. {$S-}
  3. {$V-}
  4.  
  5. Unit IOLib;
  6. { Part of BBS Onliner Interface                                                }
  7. { Copyright (C) 1990,1992 Andrew J. Mead                                       }
  8. { All Rights Reserved.                                                         }
  9.  
  10. { BBS Onliner Interface contains                                               }
  11. { Async     - low-level serial port communications interrupt handler           }
  12. { BOIDecl   - BOI standard declarations                                        }
  13. { IOLib     - standard console and port communications routines                }
  14. { IOSupp    - extended character code processing for IOLib.ReadPortKey         }
  15. { GetCmBBS  - command line parser and dropfile processing                      }
  16. { Support   - common library functions and procedures                          }
  17. { DoorLib   - information about door                                           }
  18. { Key       - registration key code shell                                      }
  19.  
  20. {  Original version 7/1/90                                                      }
  21. {  Original release version 1.0 beta 9/5/90                                    }
  22. {  Vers 1.01  9/19/90 /Q quiet local mode switch added                         }
  23. {  Vers 1.01b 9/20/90 realname usage added, /A Remote Access defined           }
  24. {  Vers 1.02  9/22/90 RA access removed, /Q switch fixed                       }
  25. {  Vers 1.03  9/23/90 /A play it Again switch added                            }
  26. {  Vers 1.10  9/24/90 /2, /F, /M, /H, /5, /6 switches added                    }
  27. {  Vers 1.11  9/29/90 beta version of /B locked baud rate                      }
  28. {  Vers 1.12 10/ 1/90 /P switch added                                          }
  29. {  Vers 1.13 10/10/90 /N switch added                                          }
  30. {  Vers 1.14 10/22/90 /B switch fixed, carrier dectect routines added          }
  31. {  Vers 1.15 10/25/90 internal reorginizations, /K added                       }
  32. {  Vers 1.16 11/ 9/90 /K fixed, F-9 abort added.                               }
  33. {  Vers 1.17 12/ 1/90 internal reorginizations.                                }
  34. {  Vers 1.17b12/ 5/90 /P fixed, /O implemented                                 }
  35. {  Vers 1.18 12/ 9/90 /O,/P verified /1,/3 implemented.                        }
  36. {  Vers 1.20 12/10/90 Initial Public Release of BBS Onliner Interface.         }
  37. {  Vers 1.21  2/25/91 Minor cosmetic changes                                   }
  38. {  Vers 1.22  4/ 7/91 PortBackground bug fixed.                                }
  39. {                     Delay rewritten.                                         }
  40. {  Vers 1.23  4/13/91 initialization and IOExit added.                         }
  41. {  Vers 1.24  5/11/92 ANSI routines modified, DisplayText added                }
  42. {                     GetCommand command line parsing bug fixed.               }
  43. {  Vers 1.25  5/19/92 CRT unit support added... release version                }
  44. {  Vers 1.26  5/20/92 more fun                                                 }
  45. {  Vers 1.27  6/11/92 registration keys added, DESQview support enhanced...    }
  46. {  Vers 1.28  6/13/92                                                          }
  47. {  Vers 1.29  6/15/92 timer interrupt added, Windoze, OS/2 awareness           }
  48. {  Vers 1.30  7/ 1/92 release version                                          }
  49. {  Vers 1.31  7/19/92 color routines optimized, TextAttr implemented           }
  50. {  Vers 1.32  7/24/92 Endgame bug fixed, Status Line handling improved         }
  51. {                     Local function key handling improved (BOI > 3000 lines)  }
  52. {                     Time remaining bug fixed                                 }
  53. {  Vers 1.33  8/ 4/92 Hall of Fame bug fixed, (ONE BBSCON) release version     }
  54. {  Vers 1.34  8/12/92 Another Hall of Fame bug fixed, release version          }
  55. {  Vers 1.35  8/16/92 /P fixed                                                 }
  56. {  Vers 1.36  8/17/92 FOSSIL routines implemented, AVATAR routines added       }
  57. {  Vers 1.37  8/18/92 additional PCBoard support added                         }
  58. {  Vers 1.38  8/26/92 minor code tightening, Minefield release                 }
  59. {  Vers 1.39 11/12/92 variables renamed and standardized, commenting improved  }
  60. {  Vers 1.40 11/19/92 known bugs squashed, more drop file formats added        }
  61. {  Vers 2.00 12/14/92 Public Release of the BBS Onliner Interface              }
  62. {                                                                              }
  63. { To be done (short list):                                                     }
  64. {   Activity logging (2.1?)                                                    }
  65. {   Enhanced Error trapping and logging (2.1?)                                 }
  66. {   Natural language files support (2.2?)                                      }
  67. {   Config file script language (3.0)                                          }
  68. {   Record Locking (2.2-3.0)                                                   }
  69. {                                                                              }
  70. { Long range possibilities                                                     }
  71. {   object orientation (2.1...)                                                }
  72. {   add comm routines for multiport boards (need info)                         }
  73. {   use of TP7 .DLLs for multinode play! (2.2...)                              }
  74. {   take advantage of TP7 pchars and other new stuff (2.1)                     }
  75. {   OS/2 version (either Claris Pascal or C/C++) (compiler availability)       }
  76. {   WinNT version (compiler availability)                                      }
  77. {                                                                              }
  78.  
  79. INTERFACE
  80.  
  81. Uses
  82.   BOIDecl,
  83.   Crt,
  84.   Dos;
  85.  
  86. { Basic Functions }
  87.   Function MIN(a,b : word) : word;
  88.   Function MAX(a,b : word) : word;
  89.   Function MINL(a,b : longint) : longint;
  90.   Function MAXL(a,b : longint) : longint;
  91.   Function HEX(hexchar : char) : byte;
  92.  
  93. {* Internal Timing *}
  94.   Procedure TIMERSET(var basetime : longint);
  95.   Function GETTIMER(var basetime : longint; val : word) : boolean;
  96.  
  97. {* File Validation and Access *}
  98.   Function EXIST(thisfile : pathstr) : boolean;
  99.   Function VALID(thisfile : pathstr) : boolean;
  100.   Procedure NOTIFYSYSOP(nfile : pathstr);
  101.   Function OPENFILE(var f:file;fsize:word;fmode:byte;faccess:facctype) : word;
  102.   Function OPENTEXT(var f : text; fmode : byte; faccess : facctype) : word;
  103.  
  104. { Output and String Functions }
  105.   Procedure SENDREMOTE(outstr : string);
  106.   Procedure SENDLOCAL(outstr : string);
  107.   Procedure SENDSTRING(outstr : string; docr : boolean);
  108.   Function INTSTR(val : longint; isize : byte) : string;
  109.   Function REALSTR(rval : real; rsize, rdec : byte) : string;
  110.   Function PADSTR(pstr : string; psize : byte) : string;
  111.   Procedure CLEANSTRING(var clean : string);
  112.   Procedure STRIPSTRING(var stripstr : string; stripset : charset);
  113.   Procedure GETSTRING(var gstr : string);
  114.  
  115. { Housecleaning }
  116.   Function SETPORT : byte;
  117.  
  118. { Display - Positional/Attribute }
  119.   Procedure SETLOCALGRAPHMODE(newmode : boi_grmode);
  120.  
  121.   Procedure GOTOPORTXY(x,y : byte);
  122.   Procedure PORTCOLOR(acolor, bcolor : byte);
  123.   Procedure TEXTPORTCOLOR(color : byte);
  124.   Procedure PORTBACKGROUND(color: byte);
  125.   Procedure GETTEXTATTR(var attribs : word);
  126.   Procedure SETTEXTATTR(attribs : word);
  127.   Procedure CHANGECOLOR(attribs : word);
  128.   Procedure UPDATESTATLINE;
  129.   Procedure CLRPORTSCR;
  130.   Procedure CLRPORTEOL;
  131.   Procedure PORTWINDOW(x1,y1,x2,y2 : byte);
  132.   Procedure PORTCOLUMNONE;
  133.  
  134. { Input Functions }
  135.   Function READPORTKEY : char;
  136.   Function PORTKEYPRESSED : boolean;
  137.   Procedure CLEARBUFFERS;
  138.  
  139. { Advanced Cursor functions }
  140.   Procedure SETPORTXY;
  141.   Procedure RESETPORTXY;
  142.  
  143. { Timeout procedure }
  144.   Function  LEFTTIME : integer;
  145.   Procedure DOTIMEOUT(ringbell : boolean);
  146.  
  147. IMPLEMENTATION
  148.  
  149. Uses
  150.   IOSupp,
  151.   Async;
  152.  
  153. Const
  154.   null  = #$00;
  155.   bell  = #$07;
  156.   ctrla = #$01; {AVATAR attrib}
  157.   ctrlb = #$02; {AVATAR blink}
  158.   ctrle = #$05;
  159.   ctrlg = #$07; {AVATAR ClrEOL}
  160.   ctrlh = #$08; {AVATAR GotoXY}
  161.   ctrll = #$0C;
  162.   ctrlv = #$16;
  163.   ctrlw = #$17; {AVATAR Switch Window}
  164.   ctrly = #$19; {AVATAR repeat}
  165.   esc   = #$1B;
  166.  
  167.   io_trylim = 10;                { file locked retry limit }
  168.  
  169.   io_basex  : byte =  1;         { internal cursor positioning variables }
  170.   io_basey  : byte =  1;
  171.   io_endx   : byte = 80;
  172.   io_endy   : byte = 24;
  173.   io_tempx  : byte =  1;
  174.   io_tempy  : byte =  1;
  175.  
  176.   io_l_avwin : byte = $00;       { active AVATAR/1 window (local)  }
  177.   io_r_avwin : byte = $00;       { active AVATAR/1 window (remote) }
  178.  
  179. Var
  180.   io_regs       : registers;     { general purpose temporary registers }
  181.   io_keyregs    : registers;
  182.  
  183.   io_workstr    : string;        { general purpose temporary variables }
  184.   io_tempbyte   : byte;
  185.   io_tempchar   : char;
  186.  
  187.   io_l_textattr : byte;          { current local text attributes  }
  188.   io_r_textattr : byte;          { current remote text attributes }
  189.  
  190. Function MIN(a, b : word) : word;   { returns the minimum of two Word values }
  191.   begin {* fMin *}
  192.     if a < b then Min := a else Min := b
  193.   end;  {* fMin *}
  194.  
  195. Function MAX(a, b : word) : word;   { returns the maximum of two Word values }
  196.   begin {* fMax *}
  197.     if a > b then Max := a else Max := b
  198.   end;  {* fMax *}
  199.  
  200. Function MINL(a, b : longint) : longint; { returns smaller longit value }
  201.   begin {* fMinL *}
  202.     if a < b then MinL := a else MinL := b
  203.   end;  {* fMinL *}
  204.  
  205. Function MAXL(a, b : longint) : longint; { returns larger longit value }
  206.   begin {* fMaxL *}
  207.     if a < b then MaxL := a else MaxL := b
  208.   end;  {* fMaxL *}
  209.  
  210. Function HEX(hexchar : char) : byte; { converts hex character into byte }
  211.   var
  212.     hexbyte : byte absolute hexchar;
  213.  
  214.   begin {* fHex *}
  215.     if hexchar in ['0'..'9'] then Hex := hexbyte AND $0F
  216.     else Hex := (hexbyte AND $0F) + $09
  217.   end;  {* fHex *}
  218.  
  219. Procedure TIMERSET(         { used with GetTimer for elapsed time routines }
  220.   var basetime : longint);    { variable to assign current time value to }
  221.  
  222.   begin {* TimerSet *}
  223.     basetime := boi_timer;
  224.   end;  {* TimerSet *}
  225.  
  226. Function GETTIMER(          { true if "val" seconds since TimerSet(basetime) }
  227.   var basetime : longint;     { variable assigned by TimerSet }
  228.       val : word) : boolean;  { target number of seconds elapsed }
  229.  
  230.   begin {* GetTimer *}
  231.     GetTimer := (boi_timer - basetime) / 18.2 > val
  232.   end;  {* GetTimer *}
  233.  
  234. Function OPENFILE(               { open an untyped file, returns IOResult }
  235.   var f       : file;              { file handle }
  236.       fsize   : word;              { record size }
  237.       fmode   : byte;              { file sharing mode }
  238.       faccess : facctype) : word;  { file opening mode }
  239.   const
  240.     busy      = 5;               { IOResult DOS file busy return code }
  241.   var
  242.     result    : word;            { result of attempt to open file }
  243.     tries     : byte;            { locked file retries count }
  244.  
  245.   begin {* fOpenFile *}
  246.     filemode := fmode;
  247.     if not dos_share then filemode := filemode AND $07;
  248.     tries := 0;
  249.     {$I-}                                    { we'll do our own checking }
  250.     repeat
  251.       begin
  252.         Inc(tries);
  253.         case faccess of                      { attempt to open file }
  254.             treset   : Reset(f,fsize);
  255.             trewrite :
  256.               begin
  257.                 Rewrite(f,fsize);
  258.                 Close(f);
  259.                 Reset(f,fsize)
  260.               end
  261.           end;
  262.         result := IOResult;                  { was it successful? }
  263.         if result = busy then if not in_dos^ then BOI_Wait
  264.                               { if busy, then give up rest of timer tick }
  265.       end
  266.     until (result <> busy) or ((tries >= io_trylim) and (result = busy));
  267.     {$I+}
  268.     OpenFile := result
  269.   end;  {* fOpenFile *}
  270.  
  271. Function OPENTEXT(               { open an untyped file, returns IOResult }
  272.   var f       : text;              { file handle }
  273.       fmode   : byte;              { file sharing mode }
  274.       faccess : facctype) : word;  { file opening mode }
  275.   const
  276.     busy      = 5;               { IOResult DOS file busy return code }
  277.   var
  278.     result    : word;            { result of attempt to open file }
  279.     tries     : byte;
  280.  
  281.   begin {* fOpenText *}
  282.     filemode := fmode;
  283.     if not dos_share then filemode := filemode AND $07;
  284.     tries := 0;
  285.     {$I-}                        { we'll do the error checking }
  286.     repeat
  287.       begin
  288.         Inc(tries);              { try to open the file }
  289.         case faccess of
  290.             treset   : Reset(f);
  291.             trewrite : Rewrite(f);
  292.             tappend  : Append(f)
  293.           end;
  294.         result := IOResult;      { did it work? }
  295.         if result = busy then if not in_dos^ then BOI_Wait
  296.                                  { if it was busy, then wait }
  297.       end
  298.     until (result <> busy) or ((tries >= io_trylim) and (result = busy));
  299.     {$I+}
  300.     OpenText := result
  301.   end;  {* fOpenText *}
  302.  
  303. Procedure NOTIFYSYSOP(    { file not found!  Tell user to bother SysOp }
  304.     nfile : pathstr);       { file that wasn't found }
  305.  
  306.   begin {* NotifySysOp *}
  307.     PortWindow(1,1,80,boi_pagelength);
  308.     ClrPortScr;
  309.     PortColor(cyan,lightgray);
  310.     PortBackground(black);
  311.     SendString('Unable to find the file : ',false);
  312.     TextPortColor(white);
  313.     SendString(nfile,true);
  314.     PortColor(cyan,lightgray);
  315.     SendString('Please notify SysOp.  Press almost any key to continue.',false);
  316.     ClearBuffers;
  317.     io_tempchar := ReadPortKey
  318.   end;  {* NotifySysOp *}
  319.  
  320. Function EXIST(                     { Check for files existence }
  321.     thisfile : pathstr) : boolean;    { filespec for file to check }
  322.   var
  323.     afile    : file;                  { temporary file handle }
  324.     isfile   : boolean;               { temporary result holder }
  325.  
  326.   begin {* fExist *}
  327.     Assign(afile,thisfile);
  328.     isfile := OpenFile(afile,1,denynone+read_only,treset) = 0;
  329.     if isfile then Close(afile);
  330.     Exist := isfile
  331.   end;  {* fExist *}
  332.  
  333. Function VALID(                     { Check filespec for validity }
  334.     thisfile : pathstr) : boolean;    { filespec to check }
  335.   var
  336.     afile    : file;                  { temporary file handle }
  337.     isgood   : boolean;               { temporary result holder }
  338.  
  339.   begin {* fValid *}
  340.     if not Exist(thisfile) then     { if the file Exists, then it is Valid }
  341.       begin
  342.         Assign(afile,thisfile);
  343.         isgood := OpenFile(afile,1,denynone+read_only,trewrite) in [0,163];
  344.         if isgood then
  345.           begin
  346.             Close(afile); { if the filespec is Valid, but it did }
  347.             Erase(afile)  { not Exist, we just created one!!!    }
  348.           end;
  349.         Valid := isgood
  350.       end
  351.     else Valid := true
  352.   end;  {* fValid *}
  353.  
  354. { this procedure should really only be called by SendString }
  355. Procedure SENDREMOTE(      { send character(s) to remote with wait }
  356.     outstr : string);        { string to send }
  357.  
  358.   begin {* SendRemote *}
  359.     for io_tempbyte := 1 to Length(outstr) do SendChar(outstr[io_tempbyte])
  360.   end;  {* SendRemote *}
  361.  
  362. { this procedure should really only be called by SendString }
  363. Procedure SENDLOCAL(       { send character(s) to local console }
  364.     outstr : string);        { string to send }
  365.  
  366.   begin {* SendLocal *}
  367.     Write(outstr)
  368.   end;  {* SendLocal *}
  369.  
  370. Procedure SENDSTRING(      { general output procedure }
  371.     outstr : string;         { string to output }
  372.     docr   : boolean);       { output newline indicator }
  373.  
  374.   begin {* SendString *}
  375.     if docr then outstr := outstr + #$0D#$0A;   { append CR/LF }
  376.     if not boi_local then SendRemote(outstr);
  377.     if boi_local or boi_echo then
  378.       begin
  379.         { if quiet mode, then strip ^Gs (bells) from output string }
  380.         if boi_quiet then for io_tempbyte := Length(outstr) downto 1 do
  381.             if outstr[io_tempbyte] = bell then Delete(outstr,io_tempbyte,1);
  382.         SendLocal(outstr)
  383.       end
  384.   end;  {* SendString *}
  385.  
  386. Function INTSTR(             { takes integer value and returns string }
  387.     val   : longint;           { value to convert }
  388.     isize : byte) : string;    { size of output string }
  389.   var
  390.     ist   : string;            { temporary string variable }
  391.  
  392.   begin {* fIntStr *}
  393.     Str(val:isize,ist);
  394.     IntStr := ist
  395.   end;  {* fIntStr *}
  396.  
  397. Function REALSTR(            { takes real value and returns string }
  398.     rval  : real;              { value to convert }
  399.     rsize : byte;              { size of output string }
  400.     rdec  : byte) : string;    { number of decimal spaces in output string }
  401.   var
  402.     ist   : string;            { temporary string variable }
  403.  
  404.   begin {* fRealStr *}
  405.     Str(rval:rsize:rdec,ist);
  406.     RealStr := ist
  407.   end;  {* fRealStr *}
  408.  
  409. Function PADSTR(             { pad text string out to psize spaces }
  410.     pstr  : string;            { string to right justify }
  411.     psize : byte) : string;    { size of output string }
  412.   var
  413.     tstr  : string;            { temporary string variable }
  414.  
  415.   begin {* fPadStr *}
  416.     if Length(pstr) > psize then PadStr := pstr
  417.     else
  418.       begin
  419.         FillChar(tstr[1],psize,32);
  420.         tstr[0] := Chr(psize);
  421.         Move(pstr[1],tstr[psize - Length(pstr) + 1],Length(pstr));
  422.         PadStr := tstr
  423.       end
  424.   end;  {* fPadStr *}
  425.  
  426. Procedure CLEANSTRING(   { remove whitespace from front and back of string }
  427.   var clean : string);     { string to clean }
  428.  
  429.   begin {* CleanString *}
  430.     while (Length(clean) > 0) and (clean[1] = ' ') do
  431.         Delete(clean,1,1);
  432.     while (Length(clean) > 0) and (clean[Length(clean)] = ' ') do
  433.         Dec(clean[0])
  434.   end;  {* CleanString *}
  435.  
  436. Procedure STRIPSTRING(     { remove specified characters from string }
  437.   var stripstr : string;     { string to strip }
  438.       stripset : charset);   { characters to remove from string }
  439.   var
  440.     sloop      : byte;
  441.  
  442.   begin {* StripString *}
  443.     for sloop := Length(stripstr) downto 1 do
  444.         if stripstr[sloop] in stripset then
  445.         Delete(stripstr,sloop,1)
  446.   end;  {* StripString *}
  447.  
  448. Function LOCALKEYPRESSED : boolean;
  449.   { indicates whether or not key on local keyboard has been pressed }
  450.  
  451.   begin {* fLocalKeyPressed *}
  452.     if KeyPressed then with io_keyregs do
  453.       begin
  454.         repeat       { remove all function keys from head of local buffer }
  455.           begin
  456.             AH := $01;  { peak at next character in buffer }
  457.             Intr($16,io_keyregs);
  458.             if AL = $00 then  { if it is a function key then... }
  459.               begin
  460.                 AH := $00;  { get next character from buffer }
  461.                 Intr($16,io_keyregs);
  462.                 CheckSecondKey(Chr(AH)) { send it off for processing }
  463.               end
  464.           end
  465.         until (not KeyPressed) or (AL <> $00);
  466.         LocalKeyPressed := (AL <> $00)
  467.       end
  468.     else LocalKeyPressed := false    { local buffer is empty }
  469.   end;  {* fLocalKeyPressed *}
  470.  
  471. Function READPORTKEY : char;    { returns (with wait) input character }
  472.   var
  473.     rkey     : char;            { input character }
  474.  
  475.   begin {* fReadPortKey *}
  476.     boi_stall := 0;        { reset inactivity timeout value }
  477.     if boi_local then      { if in local mode, then use this simpler routine }
  478.       begin
  479.         repeat BOI_Wait until LocalKeyPressed;
  480.         rkey := ReadKey
  481.       end
  482.     else
  483.       begin
  484.         while not (CharReady or LocalKeyPressed or (boi_stall >= 1092) or
  485.             not Carrier) do if not in_dos^ then
  486.             BOI_Wait;
  487.         if not (LocalKeyPressed or CharReady) and Carrier and
  488.             (boi_stall >= 1092) then
  489.           begin     { no activity for one minute }
  490.             SendString(bell,false);   { send bell to wake up user }
  491.             while not (CharReady or LocalKeyPressed or (boi_stall >= 2184) or
  492.                 not Carrier) do if not in_dos^ then
  493.                 BOI_Wait
  494.           end;
  495.         if not Carrier then DoTimeOut(false)   { see if user dropped carrier }
  496.         else if not (LocalKeyPressed or CharReady) and
  497.             (boi_stall >= 2184) then DoTimeOut(true) { two minutes-no activity }
  498.         else if CharReady then rkey := ReadBuffer
  499.         else if LocalKeyPressed then rkey := ReadKey
  500.       end;
  501.     ReadPortKey := rkey;
  502.     boi_stall := 0      { reset inactivity timeout value }
  503.   end;  {* fReadPortKey *}
  504.  
  505. Function PORTKEYPRESSED : boolean;   { is there input waiting? }
  506.   begin {* fPortKeyPressed *}
  507.     if boi_local then PortKeyPressed := LocalKeyPressed
  508.     else PortKeyPressed := LocalKeyPressed or CharReady
  509.   end;  {* fPortKeyPressed *}
  510.  
  511. Procedure CLEARBUFFERS;    { blank out local and remote input buffers }
  512.   var
  513.     cbchar : char;           { temporary input character }
  514.  
  515.   begin {* ClearBuffers *}
  516.     while LocalKeyPressed do cbchar := ReadKey;
  517.     if not boi_local then ClearInBuffer
  518.   end;  {* ClearBuffers *}
  519.  
  520. Procedure GETSTRING( { return string of input characters up to next newline }
  521.   var gstr : string);  { string to return }
  522.   var
  523.     gchar : char;    { temporary input character }
  524.  
  525.   begin {* GetString *}
  526.     gstr := '';
  527.     repeat
  528.       begin
  529.         if boi_nextchar = #$00 then
  530.             gchar := ReadPortKey     { get character }
  531.         else
  532.           begin
  533.             gchar := boi_nextchar;
  534.             boi_nextchar := #$00
  535.           end;
  536.         if gchar in [#32..#126] then { test for validity }
  537.           begin
  538.             gstr := gstr + gchar;    { append character to string }
  539.             SendString(gchar,false)  { echo character back out }
  540.           end
  541.         else if (gchar = #8) and (Length(gstr) > 0) then
  542.           begin            { if backspace and string exists... }
  543.             Delete(gstr,Length(gstr),1);
  544.             SendString(gchar + ' ' + gchar,false)
  545.           end
  546.       end
  547.     until gchar = #13;  { repeat until newline }
  548.     SendString('',true) { echo newline }
  549.   end;  {* GetString *}
  550.  
  551. { This function should only be called by GetCmBBS }
  552. Function SETPORT : byte;        { returns $00 if successful }
  553.   const
  554.     portset : boolean = false;
  555.  
  556.   begin {* fSetPort *}
  557.     if portset then SetPort := $FF { return $FF if procedure already called }
  558.     else
  559.       begin
  560.         portset := true;
  561.         if boi_local then SetPort := $00 { local mode needs no initializing }
  562.         else SetPort := IntInit     { call Async.IntInit }
  563.       end
  564.   end;  {* fSetPort *}
  565.  
  566. { this should be used to set or change boi_l_grmode }
  567. Procedure SETLOCALGRAPHMODE(   { sets up local console graphics mode }
  568.     newmode : boi_grmode);
  569.  
  570.   begin {* SetLocalGraphMode *}
  571.     boi_l_grmode := newmode;
  572.     if boi_l_grmode = gr_tpcrt then
  573.         directvideo := boi_tasker in [notask,dos5]
  574.         { if no multi-tasker present, use direct screen writes }
  575.         { otherwise use BIOS routines for local console }
  576.     else
  577.       begin
  578.         directvideo := false; { send output through CONsole driver }
  579.         Assign(output,'');
  580.         ReWrite(output);
  581.         if boi_l_grmode = gr_avt then  { additional AVATAR/1 set up }
  582.           begin
  583.             io_l_avwin := $00;     { current AVATAR window }
  584.             checkbreak := false;
  585.             SendLocal(ctrlv + '=R');  { define current AVATAR screen }
  586.             SendLocal(ctrlv + ctrlv + Chr($FF) + Chr($03) + #25#01#25#80)
  587.           end
  588.       end
  589.   end;  {* SetLocalGraphMode *}
  590.  
  591. Function AVSTR(value : byte) : string;
  592.   begin
  593.     if value <> value then AVStr := #10 + Chr(value)
  594.     else AVStr := Chr(value)
  595.   end;
  596.  
  597. Procedure GOTOPORTXY( { set current position }
  598.     x : byte;           { column to move cursor to (1..80) }
  599.     y : byte);          { row to move cursor to (1..25) }
  600.  
  601.   begin {* GotoPortXY *}
  602.     if not boi_local then case boi_r_grmode of { position remote cursor }
  603.         gr_ansi  : SendRemote(esc + '[' + IntStr(y + io_basey - 1,0) + ';' +
  604.                                         IntStr(x + io_basex - 1,0) + 'H');
  605.         gr_avt   : SendRemote(ctrlv + ctrlh + Chr(y + io_basey - 1) +
  606.                                             Chr(x + io_basex - 1))
  607.       end;
  608.     if boi_local or boi_echo then case boi_l_grmode of {position local cursor}
  609.         gr_ansi  : SendLocal(esc + '[' + IntStr(y + io_basey - 1,0) + ';' +
  610.                                        IntStr(x + io_basex - 1,0) + 'H');
  611.         gr_avt   : SendLocal(ctrlv + ctrlh + AvStr(y + io_basey - 1) +
  612.                                            AvStr(x + io_basex - 1));
  613.         gr_tpcrt : GotoXY(x,y)
  614.       end
  615.   end;  {* GotoPortXY *}
  616.  
  617. Procedure REMOTECOLOR(  { internal, sets remote text attributes }
  618.     color : byte);        { new remote attributes }
  619.  
  620.   begin {* RemoteColor *}
  621.     color := color AND $8F;  { blink must be set seperately }
  622.     { only change color if new color is not current color }
  623.     if (io_r_textattr AND $8F <> color) then case boi_r_grmode of
  624.         gr_ansi : { ANSI processing }
  625.           begin
  626.             if color > $87 then { color is intense and blinking }
  627.                 SendRemote(esc+'['+IntStr(boi_ansiarr[color],0)+';01;05m')
  628.             else if color > $7F then { color is intense }
  629.                 SendRemote(esc+'[00;'+IntStr(boi_ansiarr[color],0)+';05m')
  630.             else if color > $07 then { color is blinking }
  631.                 SendRemote(esc+'[00;'+IntStr(boi_ansiarr[color],0)+';01m')
  632.             else
  633.                 SendRemote(esc+'[00;'+IntStr(boi_ansiarr[color],0)+'m');
  634.             if io_r_textattr AND $70 <> $00 then { change background color }
  635.                 PortBackground((io_r_textattr AND $70) SHR 4)
  636.           end;
  637.         gr_avt : { AVATAR processing }
  638.           begin
  639.             if color AND $80 = $80 then SendRemote(ctrlv + ctrlb);
  640.             color := color AND $7F;
  641.             SendRemote(ctrlv + ctrla + Chr(color))
  642.           end
  643.       end;
  644.     io_r_textattr := (io_r_textattr AND $70) OR color {update text attribute}
  645.   end;  {* RemoteColor *}
  646.  
  647. Procedure LOCALCOLOR(     { internal, sets local console text attributes }
  648.     color : byte);          { new text attributes }
  649.  
  650.   begin {* LocalColor *}
  651.     color := color AND $8F;
  652.     { only change color if new color is not same as old color }
  653.     if (boi_local or boi_echo) and (io_l_textattr AND $8F <> color) then
  654.         case boi_l_grmode of
  655.         gr_ansi : { ANSI processing }
  656.           begin
  657.             if color > $87 then { color is intense and blinking }
  658.                 SendLocal(esc+'['+IntStr(boi_ansiarr[color],0)+';01;05m')
  659.             else if color > $7F then { color is intense }
  660.                 SendLocal(esc+'[00;'+IntStr(boi_ansiarr[color],0)+';05m')
  661.             else if color > $07 then { color is blinking }
  662.                 SendLocal(esc+'[00;'+IntStr(boi_ansiarr[color],0)+';01m')
  663.             else
  664.                 SendLocal(esc+'[00;'+IntStr(boi_ansiarr[color],0)+'m');
  665.             if io_l_textattr AND $70 <> $00 then { change background color }
  666.                 PortBackground((io_l_textattr AND $70) SHR 4)
  667.           end;
  668.         gr_avt : { AVATAR processing }
  669.           begin
  670.             if color AND $80 = $80 then SendLocal(ctrlv + ctrlb);
  671.             color := color AND $7F;
  672.             SendLocal(ctrlv + ctrla + Chr(color))
  673.           end;
  674.         gr_tpcrt : TextColor(color) { direct video processing }
  675.       end;
  676.     io_l_textattr := (io_l_textattr AND $70) OR color {update text attribute}
  677.   end;  {* LocalColor *}
  678.  
  679. Procedure PORTCOLOR(     { change current color conditional on color mode }
  680.     acolor : byte;         { color to be if color mode }
  681.     bcolor : byte);        { color to be if black/white mode }
  682.  
  683.   begin {* PortColor *}
  684.     if not boi_local then { change remote color }
  685.         if boi_r_color then RemoteColor(acolor)
  686.         else RemoteColor(bcolor);
  687.     if boi_local or boi_echo then { change local color }
  688.         if boi_l_color then LocalColor(acolor)
  689.         else LocalColor(bcolor);
  690.   end;  {* PortColor *}
  691.  
  692. Procedure TEXTPORTCOLOR( { change current color absolute }
  693.     color : byte);         { color to change to }
  694.  
  695.   begin {* TextPortColor *}
  696.     if not boi_local then RemoteColor(color);        { change remote color }
  697.     if boi_local or boi_echo then LocalColor(color)  { change local color }
  698.   end;  {* TextPortColor *}
  699.  
  700. Procedure PORTBACKGROUND( { change text background color }
  701.     color : byte);          { color for background to be }
  702.  
  703.   begin {* PortBackground *}
  704.     color := color AND $07;
  705.     if not boi_local then { change remote background color }
  706.       begin
  707.         if (color SHL 4) <> (io_r_textattr AND $70) then case boi_r_grmode of
  708.             gr_ansi : if color in [0..7] then {must be valid background color}
  709.                 SendRemote(esc + '[' + IntStr(boi_ansiarr[color] + 10,0) + 'm');
  710.             gr_avt :
  711.                 SendRemote(ctrlv + ctrla + Chr((io_r_textattr AND $0F) OR
  712.                 (color SHL 4)))
  713.           end;
  714.         io_r_textattr := (io_r_textattr AND $8F) OR (color SHL 4)
  715.       end;
  716.     if boi_local or boi_echo then { change local background color }
  717.       begin
  718.         if (color SHL 4) <> (io_l_textattr AND $70) then case boi_l_grmode of
  719.             gr_ansi  : if color in [0..7] then {must be valid background color}
  720.                 SendLocal(esc + '[' + IntStr(boi_ansiarr[color] + 10,0) + 'm');
  721.             gr_avt   :
  722.                 SendLocal(ctrlv + ctrla + Chr((io_l_textattr AND $0F) OR
  723.                 (color SHL 4)));
  724.             gr_tpcrt : TextBackground(color)
  725.           end;
  726.         io_l_textattr := (io_l_textattr AND $8F) OR (color SHL 4)
  727.       end
  728.   end;  {* PortBackground *}
  729.  
  730. Type
  731.   attype = array [0..1] of byte;
  732.  
  733. Procedure GETTEXTATTR(   { get current text attributes }
  734.   var attribs : word);
  735.   var
  736.     atsplit   : attype absolute attribs;
  737.  
  738.   begin {* GetTextAttr *}
  739.     atsplit[0] := io_r_textattr;
  740.     atsplit[1] := io_l_textattr
  741.   end;  {* GetTextAttr *}
  742.  
  743. Procedure SETTEXTATTR(   { set text attributes (does NOT change color) }
  744.     attribs : word);
  745.   var
  746.     atsplit : attype absolute attribs;
  747.  
  748.   begin {* SetTextAttr *}
  749.     io_r_textattr := atsplit[0];
  750.     io_l_textattr := atsplit[1]
  751.   end;  {* SetTextAttr *}
  752.  
  753. Procedure CHANGECOLOR(   { change color (by text attributes) }
  754.     attribs : word);
  755.   var
  756.     atsplit : attype absolute attribs;
  757.  
  758.   { this is usually used as a restore with data from GetTextAttr }
  759.   begin {* ChangeColor *}
  760.     if not boi_local then
  761.       begin
  762.         RemoteColor(atsplit[0]);
  763.         TextBackground((atsplit[0] AND $70) SHR 4)
  764.       end;
  765.     LocalColor(atsplit[1]);
  766.     if boi_local or boi_echo then TextBackground((atsplit[1] AND $70) SHR 4)
  767.   end;  {* ChangeColor *}
  768.  
  769. Procedure UPDATESTATLINE;  { updates user status line on local console }
  770.   var
  771.     cloop   : byte;
  772.     tempmin : word;
  773.     tempmax : word;
  774.     tempstr : string;
  775.     oldattr : word;
  776.  
  777.   begin {* UpdateStatLine *}
  778.     if not boi_local then
  779.       begin
  780.         { initialize stat line }
  781.         FillChar(io_workstr,SizeOf(io_workstr),' ');
  782.         io_workstr := '[F2] toggle ';
  783.  
  784.         { add player's name to stat line }
  785.         if boi_usename then io_workstr := io_workstr + boi_username
  786.         else io_workstr := io_workstr + 'Player Name Unknown';
  787.         if boi_usereal then io_workstr := io_workstr + ', ' + boi_realname;
  788.  
  789.         { set stat line to 79 characters }
  790.         io_workstr[0] := chr(79);
  791.  
  792.         case boi_statmode of
  793.             sm_time : if boi_usetime then
  794.               begin { show time remaining in 1/10ths of minutes }
  795.                 tempstr := 'Time: ' + tempstr;
  796.                 Move(tempstr[1],io_workstr[68],12)
  797.               end;
  798.             sm_help1 : { show help line }
  799.               begin
  800.                 io_workstr :=
  801.  '[F2] toggle   [F7] less time   [F8] more time   [F9] hang up   [F10] exit';
  802.                 Str(boi_ticks/1092:6:1,tempstr);
  803.                 io_workstr := io_workstr + tempstr
  804.               end;
  805.             sm_comm : { show current remote communications parameters }
  806.                 Move(boi_cstr[1],io_workstr[80 - Length(boi_cstr)],
  807.                     Length(boi_cstr));
  808.             sm_vid : { show current remote video mode }
  809.               begin
  810.                 tempstr := ' Remote Video: ';
  811.                 case boi_r_grmode of
  812.                     gr_ascii : tempstr := tempstr + 'ASCII';
  813.                     gr_ansi  : tempstr := tempstr + 'ANSI';
  814.                     gr_avt   : tempstr := tempstr + 'AVATAR';
  815.                     else       tempstr := tempstr + 'Unknown';
  816.                   end;
  817.                 Move(tempstr[1],io_workstr[80-Length(tempstr)],Length(tempstr))
  818.               end
  819.           end;
  820.  
  821.         if boi_l_grmode in [gr_ansi,gr_tpcrt] then
  822.           begin  { save current text attribute (windowing saves AVATAR's) }
  823.             GetTextAttr(oldattr);
  824.             ChangeColor((oldattr AND $00FF) OR $0E00)
  825.           end;
  826.         case boi_l_grmode of
  827.             gr_ansi : { ANSI processing }
  828.               begin
  829.                 SendLocal(esc + '[s');   { SetPortXY }
  830.                 SendLocal(esc+'[25;1H'); { GotoPortXY(1,25) }
  831.                 SendLocal(io_workstr);
  832.                 SendLocal(esc + '[u')    { ResetPortXY }
  833.               end;
  834.             gr_avt : { AVATAR processing }
  835.               begin
  836.                 SendLocal(ctrlv + ctrlw + Chr($FF)); { declare new window }
  837.                 SendLocal(ctrll);                    { set attributes }
  838.                 SendLocal(io_workstr);
  839.                 SendLocal(ctrlv + ctrlw + Chr(io_l_avwin)) { goto old window }
  840.               end;
  841.             gr_tpcrt : { CRT processing }
  842.               begin
  843.                 io_tempx := WhereX; { save current window settings }
  844.                 io_tempy := WhereY;
  845.                 tempmin := windmin;
  846.                 tempmax := windmax;
  847.                 Window(1,1,80,25);
  848.                 GotoXY(1,25);
  849.                 SendLocal(io_workstr);
  850.                 windmin := tempmin; { restore old window settings }
  851.                 windmax := tempmax;
  852.                 GotoXY(io_tempx,io_tempy)
  853.               end
  854.           end;
  855.         if boi_l_grmode in [gr_ansi,gr_tpcrt] then { restore old attributes }
  856.             ChangeColor(oldattr)
  857.       end;
  858.     boi_stime := boi_timer    { update stat line time keeper }
  859.   end;  {* UpdateStatLine *}
  860.  
  861. Procedure CLRPORTSCR; { clears current window }
  862.   var
  863.     cloop : byte;       { temporary looping variable }
  864.  
  865.   begin {* ClrPortScr *}
  866.     if not boi_local then case boi_r_grmode of { clear remote screen }
  867.         gr_ascii : SendRemote(#12);  { ASCII mode / formfeed }
  868.         gr_ansi  : { ANSI processing }
  869.           begin
  870.             if (io_basey = 1) and (io_endy >= boi_pagelength) then
  871.                 { if full window, clearing screen is simple }
  872.                 SendRemote(esc + '[2J')
  873.             else for cloop := 0 to io_endy - io_basey do
  874.               begin { clear each line in current window }
  875.                 SendRemote(esc + '[' + IntStr(io_endy - cloop,0) + ';1H');
  876.                 if cloop < 24 then SendRemote(esc + '[K')
  877.                 { if not bottom of screen clear EOL sequence is fine }
  878.                 else SendRemote(PadStr('',79))
  879.                 { some ANSI drivers scroll window if bottom right character }
  880.                 { is manipulated in any way }
  881.               end
  882.           end;
  883.         gr_avt : { AVATAR processing }
  884.           begin
  885.             SendRemote(ctrlv + ctrlh + Chr(io_basey) + Chr(io_basex));
  886.             SendRemote(ctrlv + ctrll + Chr(io_r_textattr AND $7F) +
  887.                 Chr(io_endy - io_basey + 1) + Chr(io_endx - io_basex + 1))
  888.           end
  889.       end;
  890.     if boi_local or boi_echo then { clear local screen }
  891.       begin
  892.         case boi_l_grmode of
  893.             gr_ascii : SendLocal(#12); { ASCII mode / formfeed }
  894.             gr_ansi  : { ANSI processing }
  895.               begin
  896.                 if (io_basey = 1) and (io_endy >= boi_pagelength) then
  897.                     { clearing full window is easy and quick }
  898.                     SendLocal(esc + '[2J')
  899.                 else for cloop := 0 to io_endy - io_basey do
  900.                   begin { clear each individual line }
  901.                     SendLocal(esc + '[' + IntStr(io_endy-cloop,0) + ';1H');
  902.                     if io_endy-cloop < 24 then SendLocal(esc + '[K')
  903.                     { if not bottom of screen clear EOL sequence is fine }
  904.                     else SendLocal(PadStr('',79))
  905.                     { some ANSI drivers scroll window if bottom right }
  906.                     { character is manipulated in any way }
  907.                   end
  908.               end;
  909.             gr_avt : { AVATAR processing }
  910.               begin
  911.                 SendLocal(ctrlv + ctrlh + AvStr(io_basey) + AvStr(io_basex));
  912.                 SendLocal(ctrlv + ctrll + Chr(io_l_textattr AND $7F) +
  913.                     Chr(io_endy - io_basey + 1) + Chr(io_endx - io_basex + 1))
  914.               end;
  915.             gr_tpcrt : ClrScr { CRT processing }
  916.           end;
  917.         if boi_usename and (not boi_local) and { update Status Line? }
  918.             (((boi_l_grmode = gr_ansi) and (io_endy >= boi_pagelength)) or
  919.             ((boi_l_grmode = gr_tpcrt) and (Hi(windmax) >= boi_pagelength))) then
  920.             UpdateStatLine
  921.       end
  922.   end;  {* ClrPortScr *}
  923.  
  924. Procedure CLRPORTEOL; { clears current line from cursor to right edge }
  925.   begin {* ClrPortEOL *}
  926.     if not boi_local then case boi_r_grmode of { clear remote line }
  927.         gr_ansi  : SendRemote(esc + '[K');
  928.         gr_avt   : SendRemote(ctrlv + ctrlg)
  929.       end;
  930.     if boi_local or boi_echo then case boi_l_grmode of { clear local line }
  931.         gr_ansi  : SendLocal(esc+'[K');
  932.         gr_avt   : SendLocal(ctrlv + ctrlg);
  933.         gr_tpcrt : ClrEOL
  934.       end
  935.   end;  {* ClrPortEOL *}
  936.  
  937. Procedure PORTWINDOW( { declare active window }
  938.     x1 : byte;          { leftmost column (1..x2) }
  939.     y1 : byte;          { topmost line (1..y1) }
  940.     x2 : byte;          { rightmost line (x1..80) }
  941.     y2 : byte);         { bottom line (y1..pagelength) }
  942.  
  943.   begin {* PortWindow *}
  944.     { use internal windowing routines for most situations }
  945.     if ((boi_echo or boi_local) and (boi_l_grmode in [gr_ansi,gr_avt])) or
  946.         ((not boi_local) and (boi_r_grmode in [gr_ansi,gr_avt])) then
  947.       begin { set screen parameters }
  948.         io_basex := x1;
  949.         io_basey := y1;
  950.         io_endx := Max(x1,Min(80,x2));
  951.         io_endy := Max(y1,Min(24,y2))
  952.       end;
  953.     { if local mode uses direct video, then use Borland's windowing locally }
  954.     if (boi_local or boi_echo) and (boi_l_grmode = gr_tpcrt) then
  955.         Window(x1,y1,x2,Min(25,y2));
  956.     GotoPortXY(1,1)
  957.   end;  {* PortWindow *}
  958.  
  959. Procedure PORTCOLUMNONE; { puts cursor on left side of screen on current line }
  960.   begin {* PortColumnOne *}
  961.     if not boi_local then case boi_r_grmode of { move remote cursor }
  962.         gr_ansi  : SendRemote(esc+'[79D');
  963.         gr_avt   : SendRemote(ctrlv + ctrly + Chr(2) + ctrlv + ctrle + Chr(79))
  964.       end;
  965.     if boi_local or boi_echo then case boi_l_grmode of { move local cursor }
  966.         gr_ansi  : SendLocal(esc+'[79D');
  967.         gr_avt   : SendLocal(ctrlv + ctrly + Chr(2) + ctrlv + ctrle + Chr(79));
  968.         gr_tpcrt : GotoXY(1,WhereY)
  969.       end
  970.   end;  {* PortColumnOne *}
  971.  
  972. Procedure SETPORTXY;      { saves current cursor position }
  973.   begin {* SetPortXY *}
  974.     if not boi_local then case boi_r_grmode of { save remote cursor }
  975.         gr_ansi : SendRemote(esc+'[s');        { ANSI processing }
  976.         gr_avt  :                              { AVATAR processing }
  977.           begin
  978.             Inc(io_r_avwin);   { declare new AVATAR window }
  979.             SendRemote(ctrlv + ctrlv + Chr(io_r_avwin) +
  980.                 Chr(io_r_textattr) + #01#01#25#80);
  981.             SendRemote(ctrlv + ctrlw + Chr(io_r_avwin)) {switch to new window}
  982.           end
  983.       end;
  984.     if boi_local or boi_echo then case boi_l_grmode of { save local cursor }
  985.         gr_ansi : SendLocal(esc+'[s');         { ANSI processing }
  986.         gr_avt  :                              { AVATAR processing }
  987.           begin
  988.             Inc(io_l_avwin);   { declare new AVATAR window }
  989.             SendLocal(ctrlv + ctrlv + Chr(io_l_avwin) +
  990.                 Chr(io_l_textattr) + #01#01#25#80);
  991.             SendLocal(ctrlv + ctrlw + Chr(io_l_avwin)) {switch to new window}
  992.           end;
  993.         gr_tpcrt :                             { CRT processing }
  994.           begin
  995.             io_tempx := WhereX;                { store cursor postion }
  996.             io_tempy := WhereY
  997.           end
  998.       end
  999.   end;  {* SetPortXY *}
  1000.  
  1001. { this should only be used after a call to SetPortXY }
  1002. Procedure RESETPORTXY;    { restore saved cursor position }
  1003.   begin {* ResetPortXY *}
  1004.     if not boi_local then case boi_r_grmode of { restore remote cursor }
  1005.         gr_ansi : SendRemote(esc + '[u');   { ANSI processing }
  1006.         gr_avt  : if io_r_avwin > $00 then  { AVATAR processing }
  1007.           begin
  1008.             Dec(io_r_avwin);     { retreat to previous AVATAR window }
  1009.             SendRemote(ctrlv + ctrlw + Chr(io_r_avwin))
  1010.           end
  1011.       end;
  1012.     if boi_local or boi_echo then case boi_l_grmode of {restore local cursor}
  1013.         gr_ansi : SendLocal(esc + '[u');     { ANSI processing }
  1014.         gr_avt  : if io_l_avwin > $00 then   { AVATAR processing }
  1015.           begin
  1016.             Dec(io_l_avwin);      { retreat to previous AVATAR window }
  1017.             SendLocal(ctrlv + ctrlw + Chr(io_l_avwin))
  1018.           end;
  1019.         gr_tpcrt : GotoXY(io_tempx,io_tempy) { direct video processing }
  1020.       end
  1021.   end;  {* ResetPortXY *}
  1022.  
  1023. Procedure DOTIMEOUT(     { BOI has timed out do to inactivity }
  1024.     ringbell : boolean);   { if not Async timout, send ^G (bell) }
  1025.  
  1026.   begin {* DoTimeOut *}
  1027.     if ringbell then SendString(bell,true);
  1028.     ClrScr;
  1029.     SendLocal('Program timeout.  ');
  1030.     if Carrier then SendLocal('No input for 2 minutes.'+#$0D#$0A)
  1031.     else SendLocal('Carrier Dropped.'+#$0D#$0A);
  1032.     SendLocal('Returning control to BBS.'+#$0D#$0A);
  1033.     Halt                  { Crank up the Exit Procedure chain }
  1034.   end;  {* DoTimeOut *}
  1035.  
  1036. Function LEFTTIME : integer;    { returns number of minutes left to play }
  1037.   begin {* fLeftTime *}
  1038.     if boi_ticks <= 0 then { time has expired }
  1039.       begin
  1040.         boi_timeover := true;
  1041.         LeftTime := -1
  1042.       end
  1043.     else LeftTime := longint(boi_ticks) div 1092 { convert to minutes }
  1044.   end;  {* fLeftTime *}
  1045.  
  1046. Var
  1047.   io_nextexit    : pointer; { pointer to hold address of next Exit procedure }
  1048.   io_oldtextattr : word;    { hold initial text attributes of local console }
  1049.  
  1050. {$F+}
  1051. Procedure IOEXIT;
  1052.   begin {* IOExit *}            { unit exit code }
  1053.     exitproc := io_nextexit;      { reset chain of Exit Procedures }
  1054.     textattr := io_oldtextattr;   { reset original text attributes }
  1055.     Window(1,1,80,25);
  1056.     GotoXY(1,25);                 { put cursor at bottom of the screen }
  1057.     ClrEOL;
  1058.     NormVideo
  1059.   end;  {* IOExit *}
  1060. {$F-}
  1061.  
  1062. begin {* uIOLib *}           { unit initialization code }
  1063.   directvideo := (boi_tasker in [notask,dos5]);
  1064.   io_oldtextattr := textattr;  { store current local text attributes }
  1065.   io_l_textattr  := textattr;  { set local text attribute variable }
  1066.   io_r_textattr  := textattr;  { set remote text attribute variable }
  1067.   io_nextexit    := exitproc;  { save current Exit Procedure chain }
  1068.   exitproc       := @IOExit    { add IOLib to Exit Procedure chain }
  1069. end.  {* uIOLib *}
  1070.